from copy import deepcopy
from random import *
from numpy import *

class Population:
	
	# sort e soma populacao vazia
	
	#the population will be the "gather" of all the possible solutions for the problem
	#pop pode ser o tamanho da populacao ou a copia de uma populacao
	
	#pop = Population(self.size_population , self.size_individual , self.evol_type)
	def __init__(self,init_type="Init",size_population=20,precision_size=[],evol_type="GA",pop=None):
		"""
		Constructor of population
		
		this constructor can be used with a copy of another population or with 
		the values of the size for each object within the population object

		.Parameters
			size_population: size of the population to be generated
			pop: population to be used in copy initialization
			precision_size: list with the size of each gene
		"""
			
		if init_type=="Copy":
			self.pop_size=deepcopy(pop.pop_size)
			self.precision_size=deepcopy(pop.precision_size)
			self.populate = deepcopy(pop.populate)
			self.best_individual = deepcopy(pop.best_individual)
			self.average_fitness = deepcopy(pop.average_fitness)
			self.standard_deviation = deepcopy(pop.standard_deviation)
			self.evol=deepcopy(pop.evol)
		
		elif init_type == "Parameter Copy":
			self.pop_size=0
			self.precision_size=pop.precision_size
			self.populate = []
			self.best_individual = None
			self.average_fitness = None
			self.standard_deviation = None
			self.evol=pop.evol
		
		elif init_type == "Init":
			self.pop_size=size_population
			self.precision_size=precision_size
			self.generate_pop()
			self.best_individual = None
			self.average_fitness = None
			self.standard_deviation = None
			
			if evol_type=="EE":
				self.evol=[random.random() for i in range(len(precision_size))]
			else:
				self.evol=random.random()
	
	def generate_pop(self):	
		"""
		Generate random population
		
		creates a new random population with the default values
		"""
		self.populate = [Individual(len(self.precision_size),self.precision_size) for l in range(self.pop_size)]
		
	def add_individual(self,individual):
		"""
		Add new individual
		
		.Parameters 
			individual: 	the individual that will be added to the population
					it has to have the same specifications of the other individuals in the population
		"""
		self.populate.append(individual)
		self.pop_size+=1
	
	def add_individuals(self,num_individuals):
		"""
		Add a number of individuals
		
		like the add_individual but adds a number of random new individuals
		
		.Parameters
			num_individuals: number of individual that you want add
		"""
		for i in range(num_individuals):
			self.populate.append(Individual(self.indi_size,self.precision_size))
		self.pop_size+=num_individuals
		
	def remove_individual(self,individual):
		"""
		Remove individual
		
		removes a selected individual from the population
		
		.Parameters
			individual: the individual that you want to remove
		"""
		self.populate.remove(individual)
		self.pop_size

	def remove_population(self):
		self.pop_size = 0
		self.populate=[]
	
	def remove_individuals(self,num_individuals):
		"""
		Remove individuals
		
		like the remove_individual but removes a number of random individuals from the population
		"""
		pass
	
	def sort_individuals(self):
		"""
		Sort individuals
		
		sort the individuals with the criteria of the function given
		
		.Parameters
			function: the function that will be used to sort the population
		"""
		
		return self.populate.sort(Individual.comparing, reverse=True)	
		
	
	def evaluation(self,fitness,number_gen):
		"""
		Update fitness
		
		updates the fitness for each individual with the function given
		
		.Parameters
			fitness: the function that will be used to avaliate the individual
		"""

		
		fitness_array = []
		for i in range(len(self.populate)):
			f = self.populate[i].eval(fitness,0)
			fitness_array.append(f)
			
			if(i==0):
				best = self.populate[i]
			elif(best.fitness<f):
				best = self.populate[i]
		
		self.best_individual = best
		self.average_fitness = mean(fitness_array)
		self.standard_deviation = std(fitness_array)
		
	def update_stats(self):
		fitness_array = []
		it=0
		for i in self.populate:
			f = i.fitness
			fitness_array.append(f)
			if(it==0):
				best = i
			elif(best.fitness<f):
				best = i
			it+=1
				
		self.best_individual = best
		self.average_fitness = mean(fitness_array)
		self.standard_deviation = std(fitness_array)

	
	def __add__(self,population1):
		aux = Population("Copy", pop=self)
		for i in population1.populate:
			aux.populate.append(i)
			self.pop_size+=1
		return aux
	
	def __getitem__(self,index):
		return self.populate[index]	
	
	def __len__(self):
		return len(self.populate)
	
	def best(self):
		return self.best_individual
	
	def average(self):
		return self.average_fitness
	
	def deviation(self):
		return self.standard_deviation
		
	def __str__(self):
		list = ""
		if(self.best_individual!=None):
			list += "Population of " + str(self.pop_size)\
			 		+ " individuals with average fitness of "\
			 		+ str(self.average_fitness) + " and standard deviation of "\
			 		+ str(self.standard_deviation) + " with best individual: \n"
			list+= str(self.best_individual) +\
			" with fitness "\
			+ str(self.best_individual.get_fitness())\
			+ "\n"
			list+= "Population Individuals:\n"
			for i in self.populate:
				list+= str(i) + "\n"
		else:
			list+= "Population of " + str(self.pop_size)\
			 		+ " individuals.\n"
			list+= "Population Individuals:\n"
			for i in self.populate:
				list+= str(i) + "\n"
				
		return list
		

class Gene(object):
	
	#in the initialization of the Gene it creates a list with gene_size size with random 0's or 1's
	#gene_input pod ser o size do gene ou o gene em si
	def __init__(self,gene_size,gene_input=None):
		if isinstance(gene_size,Gene):
			self.gene_code=gene_size
			#self.gene_code=gene_input.gene_code = deepcopy(gene_input.gene_code) 
		else:
			self.gene_code=[randint(0,1) for j in range(gene_size)]
			
	
	def __getitem__(self,index):
		return self.gene_code[index]
		
	def __setitem__(self,index,value):
		self.gene_code[index]=value
	
	def __len__(self):
		return len(self.gene_code)

	def __repr__(self):
		return str(self.gene_code)
		
	def __str__(self):
		return str(self.gene_code)




class Individual(object):

	#an individual will have a num_gene of Genes that represent the variables of the equations
	#gene pod ser o numero d genes ou o conjunto de genes k o individuo vai ter
	def __init__(self,ind_size=0,gene_size=[],algorythm_type="AG"):
		
		if isinstance(ind_size,int):
			self.genes=[Gene(gene_size[i]) for i in range(len(gene_size))]
			self.fitness = 0
			if(algorythm_type=="EE"):
				if ind_size==0:
					self.genes.append([uniform(0,1) for i in range(len(gene_size))])
				else:
					self.genes.append([uniform(0,1) for i in range(ind_size)])

		else:
			self.genes = [ind for ind in ind_size]
			self.fitness=ind_size.fitness
	
	def remove_cromo(self):
		self.genes=[]
		
	def set_cromo_list(self,list_cromo):
		self.genes=[Gene(list_cromo[i]) for i in range(len(list_cromo))]
	
	def set_cromo_gene(self,list_cromo):
		self.genes=list_cromo
	
	
	def get_cromo(self):
		return self.genes
	
	def __getitem__(self,index):
		return self.genes[index]
			
	def __setitem__(self,index,value):
		self.genes[index]=value
	
	def __len__(self):
		return len(self.genes)
	
	def __repr__(self):
		return str(self.genes)
		
	def __str__(self):
		return str(self.genes)
	
	def get_fitness(self):
		return self.fitness
	
	def eval(self, problem_func, nger):
		self.fitness = problem_func(self.genes, nger)
		return self.fitness
		
	def best_worst(self, adversary):
		if(self.fitness>adversary.fitness):
			return [self, adversary]	 
		else:
			return [adversary, self]
		
	def comparing (in1, in2):

		if(in1.fitness>in2.fitness):
			return 1	 
		else:
			return -1
		
	
if __name__== '__main__':
	p = Population(10, [5,4,2])
	